#include "Light.h"

// Point light constructor
Light::Light(float _pos[3], float _prop_ambient[4], float _prop_diffuse[4], float _prop_specular[4])
{
	pos[0] = _pos[0];
	pos[1] = _pos[1];
	pos[2] = _pos[2];

	prop_ambient[0] = _prop_ambient[0];
	prop_ambient[1] = _prop_ambient[1];
	prop_ambient[2] = _prop_ambient[2];
	prop_ambient[3] = _prop_ambient[3];

	prop_diffuse[0] = _prop_diffuse[0];
	prop_diffuse[1] = _prop_diffuse[1];
	prop_diffuse[2] = _prop_diffuse[2];
	prop_diffuse[3] = _prop_diffuse[3];

	prop_specular[0] = _prop_specular[0];
	prop_specular[1] = _prop_specular[1];
	prop_specular[2] = _prop_specular[2];
	prop_specular[3] = _prop_specular[3];

	prop_type = LIGHTTYPE_POINT;
}

// Spot light constructor
Light::Light(float _pos[3], float _prop_direction[4], float _prop_cutoff, float _prop_exponent, float _prop_ambient[4], float _prop_diffuse[4], float _prop_specular[4], bool _cast_shadow, float _shadow_map_size)
{
	pos[0] = _pos[0];
	pos[1] = _pos[1];
	pos[2] = _pos[2];

	prop_direction[0] = _prop_direction[0];
	prop_direction[1] = _prop_direction[1];
	prop_direction[2] = _prop_direction[2];
	prop_direction[3] = _prop_direction[3];

	// Normalize direction
	float t_length = 0.0f;
	t_length = sqrtf(prop_direction[0]*prop_direction[0] + 
		prop_direction[1]*prop_direction[1] + 
		prop_direction[2]*prop_direction[2]);
	
	prop_direction[0] = prop_direction[0] / t_length;
	prop_direction[1] = prop_direction[1] / t_length;
	prop_direction[2] = prop_direction[2] / t_length;

	prop_cutoff = _prop_cutoff;
	prop_exponent = _prop_exponent;

	prop_ambient[0] = _prop_ambient[0];
	prop_ambient[1] = _prop_ambient[1];
	prop_ambient[2] = _prop_ambient[2];
	prop_ambient[3] = _prop_ambient[3];

	prop_diffuse[0] = _prop_diffuse[0];
	prop_diffuse[1] = _prop_diffuse[1];
	prop_diffuse[2] = _prop_diffuse[2];
	prop_diffuse[3] = _prop_diffuse[3];

	prop_specular[0] = _prop_specular[0];
	prop_specular[1] = _prop_specular[1];
	prop_specular[2] = _prop_specular[2];
	prop_specular[3] = _prop_specular[3];

	prop_type = LIGHTTYPE_SPOT;

	casting_shadow = _cast_shadow;


	// Setup shadow FBO and texture
	if (casting_shadow)
	{
		shadow_map_size = _shadow_map_size;
		shadow_matrix = new double[16];
		int shadowMapWidth = (int)shadow_map_size;//RENDER_WIDTH * SHADOW_MAP_RATIO;
		int shadowMapHeight = (int)shadow_map_size;//RENDER_HEIGHT * SHADOW_MAP_RATIO;
		GLenum FBOstatus;

		// Try to use a texture depth component
		glGenTextures(1, &shadow_map);
		glBindTexture(GL_TEXTURE_2D, shadow_map);
		
		// GL_LINEAR does not make sense for depth texture. However, next tutorial shows usage of GL_LINEAR and PCF
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		// Remove artifact on the edges of the shadowmap
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
		
		// No need to force GL_DEPTH_COMPONENT24, drivers usually give you the max precision if available
		glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
		glBindTexture(GL_TEXTURE_2D, 0); // create a framebuffer object
		
		glGenFramebuffersEXT(1, &shadow_fbo);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, shadow_fbo);

		// Instruct openGL that we won't bind a color texture with the currently binded FBO
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);
		
		// attach the texture to FBO depth attachment point
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, shadow_map, 0);

		// check FBO status
		FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
		if(FBOstatus != GL_FRAMEBUFFER_COMPLETE_EXT)
		{
			printf("Light: GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n");
		} else {
			printf("Light: Shadow map FBO setup complete.\n");
		}
		
		// switch back to window-system-provided framebuffer
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); 
	}
}

Light::~Light()
{
	/*
	delete [] pos;
	delete [] prop_ambient;
	delete [] prop_diffuse;
	delete [] prop_specular;
	*/
	delete [] shadow_matrix;
}

void Light::enable()
{
	//glLightfv(id, GL_AMBIENT, prop_ambient);
	//glLightfv(id, GL_DIFFUSE, prop_diffuse);
	//glLightfv(id, GL_SPECULAR, prop_specular);
	//glEnable(id);
}

void Light::disable()
{
	//glEnable(id);
}

void Light::push_props(GLenum _id)
{
	glLightfv(_id, GL_AMBIENT, prop_ambient);
	glLightfv(_id, GL_DIFFUSE, prop_diffuse);
	glLightfv(_id, GL_SPECULAR, prop_specular);

	if (prop_type == LIGHTTYPE_SPOT)
	{
		glLightfv(_id, GL_SPOT_DIRECTION, prop_direction);
		glLightf(_id, GL_SPOT_CUTOFF, prop_cutoff);
		glLightf(_id, GL_SPOT_EXPONENT, prop_exponent);
	}

	if (casting_shadow)
	{
		glBindTexture(GL_TEXTURE_2D, shadow_map);
		glMatrixMode(GL_TEXTURE);
		glLoadIdentity();
		glLoadMatrixd(shadow_matrix);
		glMatrixMode(GL_MODELVIEW);
	}
}

void Light::save_shadow_matrix()
{
	// If the light is casting shadows, setup shadow map and coords
	if (casting_shadow)
	{
		 static double modelView[16];
		 static double projection[16];
		 
		 // Moving from unit cube [-1,1] to [0,1]
		 const GLdouble bias[16] = {
			 0.5, 0.0, 0.0, 0.0,
			 0.0, 0.5, 0.0, 0.0,
			 0.0, 0.0, 0.5, 0.0,
			 0.5, 0.5, 0.5, 1.0};
		
		// Grab modelview and transformation matrices
		glGetDoublev(GL_MODELVIEW_MATRIX, modelView);
		glGetDoublev(GL_PROJECTION_MATRIX, projection);
		
		glMatrixMode(GL_TEXTURE);
		glActiveTextureARB(GL_TEXTURE7);
		glLoadIdentity();
		glLoadMatrixd(bias);
		
		// concatating all matrices into one.
		glMultMatrixd (projection);
		glMultMatrixd (modelView);

		// Save matrix
		glGetDoublev(GL_TEXTURE_MATRIX, shadow_matrix);
		
		// Go back to normal matrix mode
		glMatrixMode(GL_MODELVIEW); 
	}
}

void Light::shadowmap_begin()
{

	// Setup FBO to render to
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,shadow_fbo);
	glUseProgramObjectARB(0);

	glViewport(0,0, (int)shadow_map_size, (int)shadow_map_size);
	glClear( GL_DEPTH_BUFFER_BIT);

	//Disable color rendering, we only want to write to the Z-Buffer
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective( prop_cutoff * 2.0f, shadow_map_size/shadow_map_size, 1.0, 10000.0 );

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0, 0, 0, prop_direction[0], prop_direction[1], prop_direction[2], 0, 1, 0);
	glTranslatef(-pos[0], -pos[1], -pos[2]);

	// Culling switching, rendering only backface, this is done to avoid self-shadowing
	glCullFace(GL_FRONT);
	//drawObjects();

	//push_shadow_props();

}

void Light::shadowmap_end()
{
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);

	//Enabling color write (previously disabled for light POV z-buffer rendering)
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glCullFace(GL_BACK);

}